package com.fancy.application.sys.rest.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fancy.application.common.annotation.RestApi;
import com.fancy.application.common.service.impl.BaseService;
import com.fancy.application.common.utils.IPAddressUtil;
import com.fancy.application.common.utils.RedisUtil;
import com.fancy.application.common.utils.SpringBeanUtil;
import com.fancy.application.sys.rest.entity.SysRestMain;
import com.fancy.application.sys.rest.entity.SysRestPolicy;
import com.fancy.application.sys.rest.mapper.SysRestMainMapper;
import com.fancy.application.sys.rest.service.ISysRestConstants;
import com.fancy.application.sys.rest.service.ISysRestMainService;

import com.fancy.application.sys.rest.service.ISysRestPolicyService;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 如果没有策略，则接口默认不可用
* fancyCode 自动生成v1.5
* @author wison
* 2022-08-09
* email：wurong715@163.com
*/
@Log4j2
@Service("sysRestMainService")
@Order(2)
public class SysRestMainServiceImpl extends BaseService<SysRestMainMapper,SysRestMain> implements ISysRestMainService, ISysRestConstants, ApplicationRunner {

    @Resource
    private RedisUtil redisUtil;

    /**
     * 自动注册接口定义
     * @param args
     */
    @Override
    public void run(ApplicationArguments args) {
        log.info("开始注册restService..");
        Map<String, Object>  map = SpringBeanUtil.getApplicationContext().getBeansWithAnnotation(RequestMapping.class);
        List<Map<String,String>> restScanList = new ArrayList<>(); //获取定义的接口类
        for(String key :map.keySet()){
            Object obj = map.get(key);
            Package aPackage = obj.getClass().getPackage();
            //包含在基本路径之中，不在这个
            if(aPackage.getName().contains(fdBasePackage)){
                RequestMapping annotation = obj.getClass().getAnnotation(RequestMapping.class);
                if(annotation!=null&&annotation.value().length>0){
                    String serviceUrl = annotation.value()[0];
                    //判断是否为rest-api开头，只有rest-api开头，才会被识别为接口
                    if(StringUtils.startsWithIgnoreCase(serviceUrl,fdBasePath)){
                        List<Map<String,String>> methodPrams = getApiMethods(obj);
                        methodPrams.forEach(methodParam -> {
                            String fdServicePath = methodParam.get("fdServicePath");
                            fdServicePath =serviceUrl+fdServicePath;
                            methodParam.put("fdServicePath",fdServicePath);
                            methodParam.put("fdStatus",ServiceStatus.OK.getStatus());
                            methodParam.put("fdEnable","true");
                        });
                        restScanList.addAll(methodPrams);
                    }
                }
            }
        }

        List<Map<String,String>> restDBList = this.findRestsInDataBase();
        List<Map<String,String>> deleteRestList = getModifyErrorList(restScanList,restDBList);
        saveRestList(restScanList);
        deleteRestList(deleteRestList);
        log.info("注册restService结束..");
    }

    private void deleteRestList(List<Map<String, String>> deleteRestList) {
        deleteRestList.forEach(deleteRestMap->{
            String fdId = deleteRestMap.get("fd_id");
            this.removeById(fdId);
        });
    }

    /**
     * 保存新增的
     * @param addRestList
     */
    private void saveRestList(List<Map<String, String>> addRestList) {

        addRestList.forEach(addRestMap->{
            String fdName = addRestMap.get("fdName");
            String fdServicePath = addRestMap.get("fdServicePath");
            String fdServiceKey = addRestMap.get("fdServiceKey");
            String fdPolicyId = addRestMap.get("fdPolicyId");
            String fdPolicyName = addRestMap.get("fdPolicyName");
            String fdRemark = addRestMap.get("fdRemark");
            String fdEnable = addRestMap.get("fdEnable");
            LambdaQueryWrapper<SysRestMain> queryWrapper = new QueryWrapper<SysRestMain>().lambda();
            SysRestMain sysRestMain = this.getOne(queryWrapper.eq(SysRestMain::getFdServiceKey,fdServiceKey));
            if(sysRestMain==null) sysRestMain = new SysRestMain();
            sysRestMain.setFdName(fdName);
            sysRestMain.setFdServicePath(fdServicePath);
            sysRestMain.setFdServiceKey(fdServiceKey);
            sysRestMain.setFdPolicyId(fdPolicyId);
            sysRestMain.setFdPolicyName(fdPolicyName);
            sysRestMain.setFdRemark(fdRemark);
            sysRestMain.setFdEnable(fdEnable);
            this.saveOrUpdate(sysRestMain);

        });
    }

    /**
     * 计算需要设置为异常的restservice
     * @param restScanList
     * @param restDBList
     * @return
     */
    private List<Map<String, String>> getModifyErrorList(List<Map<String, String>> restScanList, List<Map<String, String>> restDBList) {
        List<Map<String,String>> ErrorList = new ArrayList<>();
        for(Map<String,String> map : restDBList){
            String key = map.get("fd_service_key");
            Boolean serviceExist = serviceIsExistInRestScanList(key,restScanList);
            if(!serviceExist) ErrorList.add(map);
        }
        return ErrorList;
    }

    private Boolean serviceIsExistInRestScanList(String key, List<Map<String, String>> restScanList) {
        for(Map<String,String> map : restScanList){
            String fdServiceKey = map.get("fdServiceKey");
            if(StrUtil.equals(fdServiceKey,key)){
                return true;
            }
        }
        return false;
    }

    /**
     *  计算需要增加的restservice
     * @param restScanList
     * @param restDBList
     * @return
     */
    private List<Map<String, String>> getModifyAddList(List<Map<String, String>> restScanList, List<Map<String, String>> restDBList) {
        List<Map<String,String>> addedList = new ArrayList<>();
        for(Map<String,String> map : restScanList){
            String key = map.get("fdServiceKey");
            Boolean serviceExist = serviceIsExist(key,restDBList);
            if(!serviceExist) addedList.add(map);
        }
        return addedList;
    }

    private Boolean serviceIsExist(String key, List<Map<String, String>> restDBList) {
        for(Map<String,String> map : restDBList){
            String fdServiceKey = map.get("fd_service_key");
            if(StrUtil.equals(fdServiceKey,key)){
                return true;
            }
        }
        return false;
    }

    private List<Map<String, String>> findRestsInDataBase() {
        List<Map<String, String>> restDBListString = new ArrayList<>();
        List<Map<String, Object>> restDBList = this.listMaps();  //从数据库当中获取的restService列表
        restDBList.forEach(stringObjectMap -> {
            Map<String,String> stringMap = new HashMap<>();
            stringObjectMap.remove("fd_create_time");
            for(String key : stringObjectMap.keySet()){
                stringMap.put(key, (String) stringObjectMap.get(key));
            }
            restDBListString.add(stringMap);
        });
        return restDBListString;
    }

    /**
     * 获取包含API注解的方法
     * @param obj
     * @return
     */
    private List<Map<String,String>> getApiMethods(Object obj) {
        Method[] methods = obj.getClass().getDeclaredMethods();
        List<Map<String,String>> maps = new ArrayList<>();
        for(Method method:methods){
            RestApi restApi = method.getAnnotation(RestApi.class);
            GetMapping GetMappingApi = method.getAnnotation(GetMapping.class);
            PostMapping PostMappingApi = method.getAnnotation(PostMapping.class);
            String servicePath = null;
            if(restApi==null)continue;
            if(GetMappingApi!=null){
                servicePath = GetMappingApi.value()[0];
            }
            if(PostMappingApi!=null){
                servicePath = PostMappingApi.value()[0];
            }
            if(StringUtils.isEmpty(servicePath))continue;
            String name = restApi.name();
            String desc = restApi.desc();
            String key = restApi.key();
            if(StringUtils.isEmpty(key)){
                log.warn("restService 的唯一标识为空，跳过该service接口!!");
                continue;
            }
            Map<String,String> map = new HashMap<>();
            map.put("fdName",name);
            map.put("fdServiceKey",key);
            map.put("fdServicePath",servicePath);
            map.put("fdRemark",desc);
            map.put("fdMethodName",method.getName());
            maps.add(map);
        }
        return maps;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdate(SysRestMain sysRestMain) {
        String fdPolicyId = sysRestMain.getFdPolicyId();
        if(StringUtils.hasLength(fdPolicyId)){
            SysRestPolicy policy = sysRestPolicyService.getById(fdPolicyId);
            sysRestMain.setFdPolicyName(policy.getFdName());
        }
        boolean flag = super.saveOrUpdate(sysRestMain);
        //更新redis缓存
        updateCache();
        return flag;
    }

    @Resource
    private ISysRestPolicyService sysRestPolicyService;

    /**
     * 动态的计算这个请求是否能访问
     *
     * @return
     */
    @Override
    public CheckURIResult isAllowed(HttpServletRequest request) {
        SysRestMain sysRestMain = this.checkURI(request.getRequestURI());
        if(sysRestMain==null)return new CheckURIResult("未找到接口");
        if(StrUtil.isEmpty(sysRestMain.getFdPolicyId()))return new CheckURIResult("未找到对应的安全策略");
        if(!sysRestPolicyService.checkWhiteList(sysRestMain.getFdPolicyId(),request)){    //IP绑定
            return  new CheckURIResult("请求ip("+IPAddressUtil.getIPAddress(request)+")未在白名单之内");
        };
        return sysRestPolicyService.checkURI(sysRestMain.getFdPolicyId(),request);
    }

    private SysRestMain checkURI(String requestURI) {
        List<SysRestMain> sysRestMains = this.getSysRestMainList();
        for(SysRestMain sysRestMain:sysRestMains){
            if(StrUtil.equals(sysRestMain.getFdServicePath(),requestURI)){
                return sysRestMain;
            }
        }
        return null;
    }

    private List<SysRestMain> getSysRestMainList() {
        return (List<SysRestMain>) redisUtil.get(this.redisKey);
    }

    @Override
    public void updateCache(){
        //先加载策略，再加载接口列表
        if(redisUtil.hasKey(ISysRestPolicyService.redisKey)){
            redisUtil.del(ISysRestPolicyService.redisKey);
        }
        List<SysRestPolicy> policyList = sysRestPolicyService.list();
        redisUtil.set(ISysRestPolicyService.redisKey,policyList,-1);
        if(redisUtil.hasKey(ISysRestMainService.redisKey)){
            redisUtil.del(ISysRestMainService.redisKey);
        }
        List<SysRestMain> restList = this.list();
        redisUtil.set(ISysRestMainService.redisKey,restList,-1);
    }

    @Override
    public SysRestMain getRestMainByURI(String URI) {
        List<SysRestMain> sysRestMains = getSysRestMainList();
        for(SysRestMain sysRestMain:sysRestMains){
            if(sysRestMain.getFdServicePath().equals(URI)){
                return sysRestMain;
            }
        }
        return null;
    }
}
